/**
 *@description:
 *@file: post_processing.cpp
 *@author: snow-tyan
 *@email: zziywang@163.com
 *@date: 4 February 2023
 *@copyright: 2018-2022 AiRiA. All rights reserved.
 */

#include "mygl/game/post_processing.h"
#include "mygl/shader.h"
#include "mygl/texture2d.h"

#include <iostream>

namespace mygl {
namespace game {

PostProcessor::PostProcessor(std::shared_ptr<Shader> shader, GLuint width, GLuint height)
    : width(width),
      height(height),
      confuse(GL_FALSE),
      chaos(GL_FALSE),
      shake(GL_FALSE),
      ms_fbo_(0),
      fbo_(0),
      rbo_(0),
      vao_(0),
      shader_(shader),
      texture2d_(std::make_shared<Texture2D>()) {
  // Initialize renderbuffer/framebuffer object
  glGenFramebuffers(1, &ms_fbo_);
  glGenFramebuffers(1, &fbo_);
  glGenRenderbuffers(1, &rbo_);

  // Initialize renderbuffer storage with a multisampled color buffer (don't need a
  // depth/stencil buffer)
  glBindFramebuffer(GL_FRAMEBUFFER, ms_fbo_);
  glBindRenderbuffer(GL_RENDERBUFFER, rbo_);
  glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_RGB, width,
                                   height);  // Allocate storage for render buffer object
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                            rbo_);  // Attach MS render buffer object to framebuffer
  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    std::cout << "ERROR::POSTPROCESSOR: Failed to initialize MSFBO" << std::endl;
    exit(-1);
  }

  // Also initialize the FBO/texture to blit multisampled color-buffer to; used for shader
  // operations (for postprocessing effects)
  glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
  texture2d_->GenerateWithoutData(width, height);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                         texture2d_->id(),
                         0);  // Attach texture to framebuffer as its color attachment
  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    std::cout << "ERROR::POSTPROCESSOR: Failed to initialize FBO" << std::endl;
    exit(-1);
  }
  glBindFramebuffer(GL_FRAMEBUFFER, 0);

  // Initialize render data and uniforms
  this->initRenderData();
  shader_->UseProgram();
  shader_->SetUniformInt("scene", 0);
  GLfloat offset = 1.0f / 300.0f;
  GLfloat offsets[9][2] = {
      {-offset, offset},   // top-left
      {0.0f, offset},      // top-center
      {offset, offset},    // top-right
      {-offset, 0.0f},     // center-left
      {0.0f, 0.0f},        // center-center
      {offset, 0.0f},      // center - right
      {-offset, -offset},  // bottom-left
      {0.0f, -offset},     // bottom-center
      {offset, -offset}    // bottom-right
  };
  glUniform2fv(glGetUniformLocation(shader_->program_id(), "offsets"), 9,
               (GLfloat*)offsets);
  GLint edge_kernel[9] = {-1, -1, -1, -1, 8, -1, -1, -1, -1};
  glUniform1iv(glGetUniformLocation(shader_->program_id(), "edge_kernel"), 9,
               edge_kernel);
  GLfloat blur_kernel[9] = {1.0 / 16, 2.0 / 16, 1.0 / 16, 2.0 / 16, 4.0 / 16,
                            2.0 / 16, 1.0 / 16, 2.0 / 16, 1.0 / 16};
  glUniform1fv(glGetUniformLocation(shader_->program_id(), "blur_kernel"), 9,
               blur_kernel);
}

void PostProcessor::BeginRender() {
  glBindFramebuffer(GL_FRAMEBUFFER, ms_fbo_);
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);
}

void PostProcessor::EndRender() {
  // Now resolve multisampled color-buffer into intermediate FBO to store to texture
  glBindFramebuffer(GL_READ_FRAMEBUFFER, ms_fbo_);
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_);
  glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT,
                    GL_NEAREST);
  glBindFramebuffer(GL_FRAMEBUFFER,
                    0);  // Binds both READ and WRITE framebuffer to default framebuffer
}

void PostProcessor::Render(GLfloat delta_time) {
  // Set uniforms/options
  shader_->UseProgram();
  shader_->SetUniformFloat("time", delta_time);
  shader_->SetUniformFloat("confuse", confuse);
  shader_->SetUniformFloat("chaos", chaos);
  shader_->SetUniformFloat("shake", shake);
  // Render textured quad
  glActiveTexture(GL_TEXTURE0);
  texture2d_->Bind();
  glBindVertexArray(vao_);
  glDrawArrays(GL_TRIANGLES, 0, 6);
  glBindVertexArray(0);
}

void PostProcessor::initRenderData() {
  GLuint vbo;
  GLfloat vertices[] = {
      // Pos        // Tex
      -1.0f, -1.0f, 0.0f, 0.0f,  //
      1.0f,  1.0f,  1.0f, 1.0f,  //
      -1.0f, 1.0f,  0.0f, 1.0f,

      -1.0f, -1.0f, 0.0f, 0.0f,  //
      1.0f,  -1.0f, 1.0f, 0.0f,  //
      1.0f,  1.0f,  1.0f, 1.0f,
  };
  glGenVertexArrays(1, &vao_);
  glGenBuffers(1, &vbo);

  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

  glBindVertexArray(vao_);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
}

}  // namespace game
}  // namespace mygl
